
`include "common_header.verilog"

//  *************************************************************************
//  File : p8264_mld_tx.v
//  *************************************************************************
//  This program is controlled by a written license agreement.
//  Unauthorized reproduction or use is expressly prohibited.
//  Copyright (c) 2010 MorethanIP
//  Muenchner Strasse 199, 85757 Karlsfeld, Germany
//  info@morethanip.com
//  http://www.morethanip.com
//  *************************************************************************
//  Designed by : Denis Poverennyy
//  info@morethanip.com
//  *************************************************************************
//  Description : 40G PCS Multi-Lane Distribution (Tx)
//                With no following FIFO
//  Version     : $Id: p8264_mld_tx.v,v 1.10 2019/01/23 15:28:39 dk Exp $
//  *************************************************************************

module p8264_mld_tx (

        reset,
        clk,
        sw_reset,
        type_in_0,
        data_in_0,
        cgmii_clk_en,
        cgmii_tx_tsu,
        disable_mld,
        mode25,
        vl_intvl,
`ifdef MTIPPCS82_EEE_ENA 
        ram_start,
        down_count_enable,      
        down_count,
        down_count_load,        
        no_ram,
`endif
        vl_0_enc_in,
        vl_1_enc_in,
        vl_2_enc_in,
        vl_3_enc_in,        
        gb_rd,
        data_out_66_0,
        data_out_66_1,
        data_out_66_2,
        data_out_66_3,
        marker_start,
        marker_ins_cnt
                                        );

input           reset;                  //  async active high reset
input           clk;                    //  system clock
input           sw_reset;               //  Software Reset
input   [1:0]   type_in_0;              //  data 0 type (Data/Control)
input   [63:0]  data_in_0;              //  data 0 input
input           gb_rd;                  //  Gearbox clock enable 

input           disable_mld;            //  disable MLD (10G/25G mode setting)
input           mode25;                 //  configure MLD use for 25G omitting BIP insertion
input   [15:0]  vl_intvl;               //  Virtual Lane Marker Interval Setting
input   [23:0]  vl_0_enc_in;            //  Marker pattern for PCS Virtual Lane 0
input   [23:0]  vl_1_enc_in;            //  Marker pattern for PCS Virtual Lane 1
input   [23:0]  vl_2_enc_in;            //  Marker pattern for PCS Virtual Lane 2
input   [23:0]  vl_3_enc_in;            //  Marker pattern for PCS Virtual Lane 3

`ifdef MTIPPCS82_EEE_ENA 
input           ram_start;              // RAM mode
input           down_count_enable;      // Boolean variable controlling decrement of the counter down_count. This variable is set by the LPI
                                        // transmit state diagram.
input   [7:0]   down_count;             // A counter that is used in rapid alignment markers and is decremented each time a RAM is sent
                                        // while variable down_count_enable = true. The counter initial value is set by the LPI transmit state
                                        // diagram. When the down_count counter reaches zero it will set the variable down_count_done = true.   
input           down_count_load;        // write for down_count value
input           no_ram;                 // the LPI SM either in the TX_ACTIVE or TX_FW 
`endif



output  [65:0]  data_out_66_0;          // data 0 output
output  [65:0]  data_out_66_1;          // data 1 output
output  [65:0]  data_out_66_2;          // data 2 output
output  [65:0]  data_out_66_3;          // data 3 output
output          marker_start;           // marker start indication
output  [15:0]  marker_ins_cnt;         // marker insertion counter

output          cgmii_clk_en;           // cgmii clock enable
output  [1:0]   cgmii_tx_tsu;          // bit 0: Cycle start, bit 1: AM start

//-------------------------------------
// Output Signals
//-------------------------------------
wire    [65:0]  data_out_66_0;
wire    [65:0]  data_out_66_1;
wire    [65:0]  data_out_66_2;
wire    [65:0]  data_out_66_3;
wire            marker_start;

reg             cgmii_clk_en;
reg     [1:0]   cgmii_tx_tsu;          // bit 0: Cycle start, bit 1: AM start

wire    [15:0]  marker_ins_cnt;
//-------------------------------------
// Internal Signals
//-------------------------------------
reg     [1:0]   gb_rd_counter;          // Gearbox read counter to get Round Robin destribution the input 66 data over 4 lanes
reg     [63:0]  data_in_0_r;            // Stored data for the lane 0
reg     [1:0]   type_in_0_r;
reg     [63:0]  data_in_1_r;            // Stored data for the lane 1
reg     [1:0]   type_in_1_r;
reg     [63:0]  data_in_2_r;            // Stored data for the lane 2
reg     [1:0]   type_in_2_r;

reg     [63:0]  data_in_3_r;            // Stored data for the lane 3
reg     [1:0]   type_in_3_r;
                                
reg     [63:0]  data_out_0;             // Output data for lane 0
reg     [63:0]  data_out_1;             // Output data for lane 1
reg     [63:0]  data_out_2;             // Output data for lane 2
reg     [63:0]  data_out_3;             // Output data for lane 3
reg     [1:0]   type_out_0;             // Output control bits for lane 0
reg     [1:0]   type_out_1;             // Output control bits for lane 1
reg     [1:0]   type_out_2;             // Output control bits for lane 2
reg     [1:0]   type_out_3;             // Output control bits for lane 3

reg             marker_dval_nxt;        // If set - next block - Markers must be inserted
reg     [15:0]  block_cnt;              // number of blocks sent per lane

wire    [23:0]  vl_0_enc;               // VL 0 24-bit encoding
wire    [23:0]  vl_1_enc;               // VL 1 24-bit encoding 
wire    [23:0]  vl_2_enc;               // VL 2 24-bit encoding 
wire    [23:0]  vl_3_enc;               // VL 3 24-bit encoding 
wire    [65:0]  marker_0;               // alignment marker 0 with zero bip fields
wire    [65:0]  marker_1;               // alignment marker 1 with zero bip fields 
wire    [65:0]  marker_2;               // alignment marker 2 with zero bip fields 
wire    [65:0]  marker_3;               // alignment marker 3 with zero bip fields 
wire    [65:0]  marker_0bip;            // Complete alignment marker 0
wire    [65:0]  marker_1bip;            // Complete alignment marker 1 
wire    [65:0]  marker_2bip;            // Complete alignment marker 2 
wire    [65:0]  marker_3bip;            // Complete alignment marker 3
wire    [7:0]   bip8_0;                 // Combinatorial BIP field for lane 0 
wire    [7:0]   bip8_1;                 // Combinatorial BIP field for lane 1 
wire    [7:0]   bip8_2;                 // Combinatorial BIP field for lane 2 
wire    [7:0]   bip8_3;                 // Combinatorial BIP field for lane 3 
reg     [7:0]   bip8_0_r;               // Registered BIP field for lane 0  
reg     [7:0]   bip8_1_r;               // Registered BIP field for lane 1  
reg     [7:0]   bip8_2_r;               // Registered BIP field for lane 2  
reg     [7:0]   bip8_3_r;               // Registered BIP field for lane 3 
wire    [65:0]  data_out_66_0_bip;      // without the disable mux (for better timing)
 
`ifdef MTIPPCS82_EEE_ENA 
reg   [7:0]     down_counter;           // down counter to generate BIP in the RAM mode
reg             ram_period;             // if 1 - RAM, otherwise standard distance
wire    [7:0]   m0_lane0;               // M0 value to produce CD field for RAM 0
wire    [7:0]   m0_lane1;               // M0 value to produce CD field for RAM 1
wire    [7:0]   m0_lane2;               // M0 value to produce CD field for RAM 2
wire    [7:0]   m0_lane3;               // M0 value to produce CD field for RAM 3
`endif


reg     [4:0]   cycle_cnt;              // Time stamp period counter	



// Round Robin distribution input block over four lanes

always @(posedge clk or posedge reset)
begin
        if (reset == 1'b1)
        begin
                data_in_0_r     <= 64'b0;
                type_in_0_r     <= 2'b0;
                data_in_1_r     <= 64'b0;
                type_in_1_r     <= 2'b0;                
                data_in_2_r     <= 64'b0;
                type_in_2_r     <= 2'b0;                                 
                data_in_3_r     <= 64'b0;
                type_in_3_r     <= 2'b0;  
        end
        else
        begin
                if (cgmii_clk_en == 1'b1)
                begin
                        if (gb_rd_counter == 2'd1)
                        begin
                                data_in_0_r     <= data_in_0;
                                type_in_0_r     <= type_in_0;
                        end
                        else if (gb_rd_counter == 2'd2)
                        begin
                                data_in_1_r     <= data_in_0;
                                type_in_1_r     <= type_in_0;
                        end              
                        else if (gb_rd_counter == 2'd3)
                        begin
                                data_in_2_r     <= data_in_0;
                                type_in_2_r     <= type_in_0;
                        end                                               

                        else/// if (gb_rd_counter == 2'd0)
                        begin
                                data_in_3_r     <= data_in_0;
                                type_in_3_r     <= type_in_0;
                        end 
                end
        end                
end                
                 

// Muxing between data and Markers

always @ (*)
begin
        if  (marker_dval_nxt == 1'b0)

        begin
                data_out_0 = data_in_0_r;
                data_out_1 = data_in_1_r;
                data_out_2 = data_in_2_r;                        
                type_out_0 = type_in_0_r;
                type_out_1 = type_in_1_r;
                type_out_2 = type_in_2_r;                        
                if (cgmii_clk_en == 1'b1)
                begin 
                        data_out_3 = data_in_0;  
                        type_out_3 = type_in_0;
                end
                else
                begin 
                        data_out_3 = data_in_3_r;  
                        type_out_3 = type_in_3_r;
                end                                                                
        end
        else
        begin
                data_out_0 = marker_0bip[65:2];
                data_out_1 = marker_1bip[65:2];
                data_out_2 = marker_2bip[65:2];                        
                data_out_3 = marker_3bip[65:2];
                                        
                type_out_0 = 2'b 01;
                type_out_1 = 2'b 01;
                type_out_2 = 2'b 01;                        
                type_out_3 = 2'b 01;                                                 
        end                     
end


// Concatenate data and type fields for the final output

assign data_out_66_0 = (disable_mld == 1'b 1) ? {data_out_3, type_out_3} :      // column 3 is directly following input allowing bursts
                                                {data_out_0, type_out_0};

assign data_out_66_0_bip = {data_out_0, type_out_0};    // avoid the disable MUX going into the BIP which is not relevant

assign data_out_66_1 = {data_out_1, type_out_1};
assign data_out_66_2 = {data_out_2, type_out_2};
assign data_out_66_3 = {data_out_3, type_out_3};




// Block Count - to calculation time slot for AM insertion 

always @(posedge clk or posedge reset)
begin
        if (reset == 1'b 1)
        begin
                block_cnt <= 16'h 3FFF;	//  count from 0 to 16383 (standard)	
        end
        else
        begin
                if (sw_reset == 1'b 1)
                begin
                        block_cnt <= vl_intvl;
                end
                else if (gb_rd == 1'b 1 )
                begin
                        if ((block_cnt >= vl_intvl) 
`ifdef MTIPPCS82_EEE_ENA                        
                        || (ram_period == 1'b1 && block_cnt == 16'd 15)
`endif                        
                                                                                                        )
                        begin
                                block_cnt <= 16'b0;	
                        end
                        else
                        begin
                                block_cnt <= block_cnt + 16'b1;	
                        end
		end               
      end
end

assign marker_ins_cnt = block_cnt;

// Set Flag that next block is AM

always @(posedge clk or posedge reset)
begin
        if (reset == 1'b 1)
        begin
                marker_dval_nxt <= 1'b 1;	
        end
        else
        begin		
                if (sw_reset == 1'b 1)
                begin
                        marker_dval_nxt <= 1'b 1;
		end
                else if (disable_mld == 1'b 1 )
                begin
                        marker_dval_nxt <= 1'b 0;
                end
                else if (gb_rd == 1'b 1 )
                begin
                        if ( (block_cnt == (vl_intvl - 16'b1) ) 
`ifdef MTIPPCS82_EEE_ENA                        
                        || (ram_period == 1'b1 && block_cnt == 16'd 14)
`endif                           
                                                                                                        )
                        begin
                                marker_dval_nxt <= 1'b 1;	
                        end
                        else
                        begin
                                marker_dval_nxt <= 1'b 0;	
                        end

                end                
      end
end

assign marker_start = marker_dval_nxt;

// Clock enable generation
// To get 4 words ready for next  gb_rd, clock enable (cgmii_clk_en) is 
// set for four clocks. 
// cgmii_clk_en must not be generated if nexts blocks are Markers
// 

always @(posedge clk or posedge reset)
begin
        if (reset == 1'b 1)
        begin
                cgmii_clk_en  <= 1'b0;
                gb_rd_counter <= 2'd0;

        end
        else
        begin
                if (sw_reset == 1'b 1)
                begin
                        cgmii_clk_en <= 1'b0;
                        gb_rd_counter <= 2'd0;
                end
                else if( disable_mld == 1'b 1 )
                begin
                        cgmii_clk_en  <= gb_rd;         // 1 delayed
                        gb_rd_counter <= 2'd0;
                end
                else
                begin
                        if ( gb_rd == 1'b1  && (marker_dval_nxt == 1'b0) )
                        begin
                                 cgmii_clk_en <= 1'b1;
                                 gb_rd_counter <= 2'd1;                                
                        end
                        else if (gb_rd_counter != 2'd0)
                        begin
                                 cgmii_clk_en <= 1'b1;
                                 gb_rd_counter <= gb_rd_counter + 2'b1;  
                        end
                        else
                        begin
                                 cgmii_clk_en  <= 1'b0;
                                 gb_rd_counter <= 2'd0;                                 
                        end 
                end
        end

end




always @(posedge clk or posedge reset)
begin
        if (reset == 1'b 1)
        begin
                cycle_cnt     <= 5'h 0;	
                cgmii_tx_tsu  <= 2'b0;
        end
        else
        begin
                if (sw_reset == 1'b 1)
                begin
                        cycle_cnt     <= 5'h 0;	
                        cgmii_tx_tsu  <= 2'b0;
                end
                else  if ( gb_rd == 1'b1)
                begin 
                        if (marker_dval_nxt == 1'b1 || cycle_cnt == 5'd19)
                        begin
                                cgmii_tx_tsu[0] <= 1'b 1;
                                cycle_cnt <= 5'd0;
                                if (marker_dval_nxt == 1'b1)
                                begin
                                        cgmii_tx_tsu[1] <= 1'b 1;        
                                end
                                else
                                begin
                                        cgmii_tx_tsu[1] <= 1'b 0;
                                end
                        end
                        else
                        begin
                                cycle_cnt <= cycle_cnt + 5'd1;
                                cgmii_tx_tsu <= 2'b0;        
                        end                                
                end
                else
                begin
                        cgmii_tx_tsu <= 2'b0;
                end
        end

end





//  The block counter 0..16383. The 16384th is the marker slot.
//  other distance is programmable.

//  Alignment Marker
// 
//   2-bit   32-bit         32-bit
//    01 2           33 34          65
//   +--+--------------+--------------+
//   |10|      VL      |     ~VL      |
//   +--+--------------+--------------+
// 
//  P8023ba
// 
//  40GBASE-R:
//   VL 32-bit encoding
//   0: 90, 76, 47, (BIP)
//   1: F0, C4, E6, (BIP)
//   2: C5, 65, 9B, (BIP)
//   3: A2, 79, 3D, (BIP)


`ifdef MTIPPCS82_EEE_ENA
       	
assign vl_0_enc = (ram_period == 1'b1 && down_counter != 8'b0)? ~ vl_0_enc_in :  vl_0_enc_in; 
assign vl_1_enc = (ram_period == 1'b1 && down_counter != 8'b0)? ~ vl_1_enc_in :  vl_1_enc_in; 
assign vl_2_enc = (ram_period == 1'b1 && down_counter != 8'b0)? ~ vl_2_enc_in :  vl_2_enc_in; 
assign vl_3_enc = (ram_period == 1'b1 && down_counter != 8'b0)? ~ vl_3_enc_in :  vl_3_enc_in;

`else

assign vl_0_enc = vl_0_enc_in; 
assign vl_1_enc = vl_1_enc_in; 
assign vl_2_enc = vl_2_enc_in; 
assign vl_3_enc = vl_3_enc_in;

`endif


assign marker_0 = { 8'b0, ~vl_0_enc, 8'b0, vl_0_enc, 2'b 01}; 
assign marker_1 = { 8'b0, ~vl_1_enc, 8'b0, vl_1_enc, 2'b 01}; 
assign marker_2 = { 8'b0, ~vl_2_enc, 8'b0, vl_2_enc, 2'b 01}; 
assign marker_3 = { 8'b0, ~vl_3_enc, 8'b0, vl_3_enc, 2'b 01}; 

`ifdef MTIPPCS82_EEE_ENA

assign  m0_lane0 = 8'h90;
assign  m0_lane1 = 8'hF0;
assign  m0_lane2 = 8'hC5;
assign  m0_lane3 = 8'hA2;

`endif 


// BIP calculation 
// In the RAM mode BIPs fields are used for the down counter
// For 25G-RSFEC (108.5.2.4) bip fields are filled with constant 0x33.

assign bip8_0 = mode25==1'b 1 ? 8'h 33 : BIP8_CALC(data_out_66_0_bip, bip8_0_r); 
assign bip8_1 = mode25==1'b 1 ? 8'h 33 : BIP8_CALC(data_out_66_1, bip8_1_r); 
assign bip8_2 = mode25==1'b 1 ? 8'h 33 : BIP8_CALC(data_out_66_2, bip8_2_r); 
assign bip8_3 = mode25==1'b 1 ? 8'h 33 : BIP8_CALC(data_out_66_3, bip8_3_r); 

always @(posedge clk or posedge reset)
begin
        if (reset == 1'b 1)
        begin
                bip8_0_r <= 8'b 0;	
                bip8_1_r <= 8'b 0;	
                bip8_2_r <= 8'b 0;	
                bip8_3_r <= 8'b 0;	
        end
        else
        begin
                if (marker_dval_nxt == 1'b 1 && gb_rd == 1'b 1)
                begin
                        bip8_0_r <= 8'b 00001000;	
                        bip8_1_r <= 8'b 00001000;
                        bip8_2_r <= 8'b 00001000;	
                        bip8_3_r <= 8'b 00001000;
                end
                else if (gb_rd == 1'b 1 )
                begin
`ifdef MTIPPCS82_EEE_ENA
                        if (ram_period == 1'b1 && down_counter != 8'b0)
                        begin
                                bip8_0_r <= m0_lane0 ^ down_counter;                                  
                                bip8_1_r <= m0_lane1 ^ down_counter;       
                                bip8_2_r <= m0_lane2 ^ down_counter;         
                                bip8_3_r <= m0_lane3 ^ down_counter;
                        end         	
                        else
`endif
                        begin
                                bip8_0_r <= bip8_0;
                                bip8_1_r <= bip8_1;         
                                bip8_2_r <= bip8_2;         
                                bip8_3_r <= bip8_3;         	
                        end
                end
      end
end

// Final Markers

assign marker_0bip = {~bip8_0_r, marker_0[57:34], bip8_0_r, marker_0[25:0]}; 
assign marker_1bip = {~bip8_1_r, marker_1[57:34], bip8_1_r, marker_1[25:0]}; 
assign marker_2bip = {~bip8_2_r, marker_2[57:34], bip8_2_r, marker_2[25:0]}; 
assign marker_3bip = {~bip8_3_r, marker_3[57:34], bip8_3_r, marker_3[25:0]}; 




// Down Counter for EEE support
// two modes
// 1. Reproduces load value (down_count)
// 2. Counts down every marker_dval_nxt if externally down_count_enable set

`ifdef MTIPPCS82_EEE_ENA
always @(posedge clk or posedge reset)
begin
        if (reset == 1'b 1)
        begin
                down_counter    <= 8'd255;
                ram_period      <= 1'b0;		
        end
        else 
        begin
                if (sw_reset == 1'b 1 || disable_mld == 1'b 1)
                begin
                        down_counter    <= 8'd255;
                        ram_period      <= 1'b0;	
                end
                else
                begin
                        if (ram_start == 1'b1)
                        begin
                                ram_period      <= 1'b1;        
                        end
                        else if ((down_count_enable == 1'b1 && down_counter == 8'd0) || no_ram == 1'b1)
                        begin
                                ram_period      <= 1'b0;        
                        end

                        if (down_count_load == 1'b1)
                        begin
                                down_counter      <= down_count;        
                        end
                        else if (down_count_enable == 1'b1 && (marker_dval_nxt == 1'b 1 && gb_rd == 1'b 1) )
                        begin
                                down_counter      <= down_counter - 8'd1;        
                        end                        
                        
                end
        end
 end        	
`endif



//  BIP-8 Calculation
// 
//  BIP-8 for any alignment marker is always "00001000" because of
//  bit-wise inversion of data portion and "01" from sync header.
// 

function [7:0] BIP8_CALC;
input   [65:0] din; 
input   [7:0] bin; 

begin
   BIP8_CALC[0] =          din[2] ^ din[10] ^ din[18] ^ din[26] ^ din[34] ^ din[42] ^ din[50] ^ din[58] ^ bin[0];
   BIP8_CALC[1] =          din[3] ^ din[11] ^ din[19] ^ din[27] ^ din[35] ^ din[43] ^ din[51] ^ din[59] ^ bin[1];
   BIP8_CALC[2] =          din[4] ^ din[12] ^ din[20] ^ din[28] ^ din[36] ^ din[44] ^ din[52] ^ din[60] ^ bin[2];
   BIP8_CALC[3] = din[0] ^ din[5] ^ din[13] ^ din[21] ^ din[29] ^ din[37] ^ din[45] ^ din[53] ^ din[61] ^ bin[3];
   BIP8_CALC[4] = din[1] ^ din[6] ^ din[14] ^ din[22] ^ din[30] ^ din[38] ^ din[46] ^ din[54] ^ din[62] ^ bin[4];
   BIP8_CALC[5] =          din[7] ^ din[15] ^ din[23] ^ din[31] ^ din[39] ^ din[47] ^ din[55] ^ din[63] ^ bin[5];
   BIP8_CALC[6] =          din[8] ^ din[16] ^ din[24] ^ din[32] ^ din[40] ^ din[48] ^ din[56] ^ din[64] ^ bin[6];
   BIP8_CALC[7] =          din[9] ^ din[17] ^ din[25] ^ din[33] ^ din[41] ^ din[49] ^ din[57] ^ din[65] ^ bin[7];
end
endfunction

// ----------
// ASSERTIONS
// ----------

`ifdef MTIPASSERTIONS                  // enable assertions
// synthesis translate_off
// synopsys translate_off

reg[31:0] asrt_cnt;

always @(posedge clk or posedge reset)
begin:assertions
        
        if (reset == 1'b 1)
        begin
                asrt_cnt <= 0;
        end
        else
        begin
                if( sw_reset==1'b 1 ) asrt_cnt <= 0;
                else if(asrt_cnt <= 300) asrt_cnt <= asrt_cnt+1;        // delay messages after reset for some time during configuration changes 
                
                if( asrt_cnt>300 && !disable_mld )
                begin
                        if( gb_rd && gb_rd_counter != 2'd 0 )
                        begin
                                $display("%t - ASSERT [p8264_mld_tx] [%m]: WARNING Gearbox reads from MLD too fast (once in 4 cycle required) gb_rd_counter=%0d",$time, gb_rd_counter);
                                `ifdef MTIPASSERTIONS_STOP_ON_ERRORS
                                //$stop(1);
                                `elsif MTIPASSERTIONS_FINISH_ON_ERRORS
                                //$finish();
                                `endif
                        end
                end
                
                
        end
end

// synopsys translate_on
// synthesis translate_on
`endif

endmodule // module p8264_mld_tx